﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation either version
 * 2 of the License, or (at your option) any later version.
 * 
 * ExtremeML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Reflection;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;

namespace ExtremeML.Builder.Survey
{
	/// <summary>
	/// Encapsulates metadata extracted from the Html help topic file for an Sdk class.
	/// </summary>
	internal class ParsedTopic
	{
		private const string ExtractTypePattern = @"<title>(?<class>\w+)[\w\s]*\((?<namespace>[\w\.]+)\)";
		private const string ExtractSchemaPattern = @"<pre>(?=&lt;complexType)(?<schema>[\w\W]*?)</pre>";

		private ParsedTopic(
			Type type,
			string content,
			XNode schema)
		{
			Type = type;
			Content = content;

			if (schema != null)
			{
				SchemaChildren = ExtractChildElementUsages(schema);
			}
		}

		/// <summary>
		/// Gets the <see cref="Type"/> supported by this instance.
		/// </summary>
		public Type Type { get; private set; }

		/// <summary>
		/// Gets a dictionary that represents the permitted child
		/// elements and their schema-defined occurence constraints.
		/// </summary>
		public Dictionary<string, ChildElementUsage> SchemaChildren { get; private set; }


		/// <summary>
		/// Gets the help topic Html file content.
		/// </summary>
		public string Content { get; private set; }

		/// <summary>
		/// Creates and returns a new <see cref="ParsedTopic"/> from data in the specified topic file.
		/// </summary>
		/// <param name="file">A <see cref="FileInfo"/> that specifies the path to the topic file.</param>
		public static ParsedTopic Create(
			FileInfo file)
		{
			var content = ReadTopicFile(file);
			var type = ExtractType(content);

			if (!Utility.IsElementType(type))
			{
				return null;
			}
			else
			{
				try
				{
					return new ParsedTopic(type, content, ExtractSchema(content));
				}
				catch
				{
					Debug.WriteLine(string.Format("Malformed Xml in help topic file {0}.", file));
					return null;
				}
			}
		}

		/// <summary>
		/// Returns the content of the specified file as a string.
		/// </summary>
		private static string ReadTopicFile(
			FileSystemInfo file)
		{
			using (var stream = new FileStream(file.FullName, FileMode.Open))
			using (var reader = new StreamReader(stream))
			{
				return reader.ReadToEnd();
			}
		}

		/// <summary>
		/// Returns a <see cref="Type"/> that represents the type targeted by the passed Html help topic.
		/// </summary>
		private static Type ExtractType(
			string content)
		{
			var match = Regex.Match(content, ExtractTypePattern);
			var assembly = Assembly.GetAssembly(typeof(OpenXmlElement));
			return assembly.GetType(match.Groups["namespace"].Value + "." + match.Groups["class"].Value);
		}

		/// <summary>
		/// Returns an <see cref="XElement"/> containing the schema snippet embedded in the passed Html help topic.
		/// </summary>
		private static XElement ExtractSchema(
			string content)
		{
			var match = Regex.Match(content, ExtractSchemaPattern);
			if (match.Groups["schema"].Captures.Count > 0)
			{
				return XElement.Parse(XmlUnescape(match.Groups["schema"].Value));
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Parses the passed schema snippet and builds a dictionary of
		/// permitted child element types and their occurence constraints.
		/// </summary>
		private static Dictionary<string, ChildElementUsage> ExtractChildElementUsages(
			XNode markup)
		{
			var result = new Dictionary<string, ChildElementUsage>();

			foreach (var child in markup.XPathSelectElements(".//sequence/element[@name]"))
			{
				var name = child.Attribute("name").Value;
				var schemaType = child.Attribute("type").Value;
				var min = child.Attribute("minOccurs");
				var max = child.Attribute("maxOccurs");

				result.Add(name, new ChildElementUsage
				{
					SchemaElementName = name,
					SchemaTypeName = schemaType,
					MinOccurs = min == null ? 1 : int.Parse(min.Value),
					MaxOccurs = max == null ? 1 : max.Value == "unbounded" ? -1 : int.Parse(max.Value)
				});
			}

			return result;
		}

		/// <summary>
		/// Converts Xml-escaped characters in the passed string to the raw equivalents.
		/// </summary>
		private static string XmlUnescape(
			string data)
		{
			data = data.Replace("&lt;", "<");
			data = data.Replace("&gt;", ">");
			data = data.Replace("&amp;", "&");
			data = data.Replace("&quot;", "\"");
			data = data.Replace("&apos;", "'");
			return data;
		}
	}
}
